/**
 * @author Justin Akin
 * @version Action Pals Core Library 0.1.0
 */
package com.actionpals.managers
{	
	import com.actionpals.base.BaseClass;
	import com.actionpals.managers.drag.DragInfo;
	
	import flash.display.DisplayObject;
	import flash.display.Stage;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.Dictionary;
	
	/**
	 * 
	 */
	public class DragManager extends BaseClass
	{
			
	//--------------------------------------------------------------------------------
	//
	//	Protected Static Member Variables
	//
	//--------------------------------------------------------------------------------
	
		protected static var singleton:DragManager;
		
	//--------------------------------------------------------------------------------
	//
	//	Protected Member Variables
	//
	//--------------------------------------------------------------------------------
	
		protected var stage:Stage;
		protected var list:Dictionary;
		protected var dragFlag:Boolean;
	
	//--------------------------------------------------------------------------------
	//
	//	Constructor
	//	NOTE: AS3 does not allow for private or protected constructors
	//
	//--------------------------------------------------------------------------------
	
		/**
		 * this.singleton constructor.  Can only be called by the static getInstance method.
		 * 
		 * @param caller	The function to call the Dragger constructor function
		 */
		public function DragManager( caller:Function = null )
		{	
            if( caller != DragManager.getInstance )
                throw new Error ( "this.singleton is a this.singleton class, use getInstance() instead" );
			
            if ( DragManager.singleton != null )
                throw new Error( "Only one this.singleton instance should be instantiated" );	
			
			this.list = new Dictionary();
			this.dragFlag = false;	
		}
		
	//--------------------------------------------------------------------------------
	//
	//	Public Static Functions
	//
	//-------------------------------------------------------------------------------- 
        
		/**
         * Serves as a clean wrapper for getInstance
         */    
        public static function get dragManager():DragManager
        {
        	return getInstance();
        }
		
	//--------------------------------------------------------------------------------
	//
	//	Protected Static Functions
	//
	//-------------------------------------------------------------------------------- 
        
		/**
         * Creates a new instance of Dragger if one does not currently exist.
         * 
         * @return Dragger
         */
        protected static function getInstance():DragManager
        {	
            if ( singleton == null ) singleton = new DragManager( arguments.callee );
            
            return singleton;   
        }
		
	//--------------------------------------------------------------------------------
	//
	//	Public Functions
	//
	//-------------------------------------------------------------------------------- 
		
		public function getDragInfo( target:DisplayObject ):DragInfo
		{
			return this.list[ target ];
		}
		
		/**
		 * Adds a dragInfo object to the this.list collection.  If a drag session is not 
		 * already in session then one will be started.
		 * 
		 * @param dragInfo		The dragInfo object to be added to the this.list collection.
		 */
		public function add( target:DisplayObject, bounds:Rectangle = null, lockCenter:Boolean = false ):void
		{	
			var dragInfo:DragInfo = new DragInfo( target, bounds, lockCenter );
			var mousePoint:Point = target.localToGlobal( new Point( target.mouseX, target.mouseY ) );
			var targetPoint:Point = new Point( target.x, target.y );
			
			dragInfo.mouseStartPoint = mousePoint;
			dragInfo.targetStartPoint = targetPoint;
			
			this.list[ target ] = dragInfo;
			
			if( !this.dragFlag ) startDrag();
		}
		
		/**
		 * Removes a specific dragInfo object from the this.list collection.  If the this.list
		 * collection is then empty the current drag session will be ended.
		 * 
		 * @param		The dragInfo Object to be removed from the this.list collection.
		 * @return		A boolean value representing whether or not the dragInfo was
		 * 				successfully removed from the this.list collection.
		 */
		public function remove( target:DisplayObject ):DragInfo
		{
			var empty:Boolean = true;
			var dragInfo:DragInfo = this.list[ target ];
			
			if( !this.stage ) this.stage = target.stage;
			
			delete this.list[ target ];
			
			for( var i:String in this.list )
			{
				empty = false;
				break;
			}
			
			if( empty ) endDrag();
			
			return dragInfo;
		}
		
		/**
		 * Removes all objects from the this.list collection and ends the current drag session.
		 */
		public function removeAll():void
		{	
			this.list = new Dictionary();
			endDrag();
		}
		
	//--------------------------------------------------------------------------------
	//
	//	Protected Functions
	//
	//-------------------------------------------------------------------------------- 
		
		/**
		 * Starts a new drag session.
		 */
		protected function startDrag():void
		{	
			if( !this.stage )
			{
				for( var i:String in this.list )
				{
					this.stage = DragInfo( this.list[ i ] ).target.stage;
					break;
				}
			}
			
			this.stage.addEventListener( MouseEvent.MOUSE_MOVE, onDrag );
			this.dragFlag = true;
		}
		
		/**
		 * Ends the current drag session.
		 */		
		protected function endDrag():void
		{				
			this.stage.removeEventListener( MouseEvent.MOUSE_MOVE, onDrag );
			this.dragFlag = false;	
		}
		
		/**
		 * Repositions the dragged object.
		 * 
		 * @param dragInfo		The object containing data relevant to the object
		 * 						being dragged.
		 * @param index			The position of dragInfo in array.
		 * @param array			Represents the this.list collection.
		 */
		protected function dragIt( dragInfo:DragInfo ):void
		{	
			var target:DisplayObject = dragInfo.target;
			var lockCenter:Boolean = dragInfo.lockCenter;
			var bounds:Rectangle = dragInfo.bounds;
			var targetStartPoint:Point = dragInfo.targetStartPoint;
			var mouseStartPoint:Point = dragInfo.mouseStartPoint;
			var mousePoint:Point = target.localToGlobal( new Point( target.mouseX, target.mouseY ) );
			var xOffSet:Number = mousePoint.x - mouseStartPoint.x;
			var yOffSet:Number = mousePoint.y - mouseStartPoint.y;
			var newX:Number = targetStartPoint.x + xOffSet;
			var newY:Number = targetStartPoint.y + yOffSet;
			var temp:Rectangle = new Rectangle( newX, newY, target.width, target.height );
			
			if( temp.right > bounds.right ) temp.x = bounds.right - temp.width;
			if( temp.left < bounds.left ) temp.x = bounds.x;
			if( temp.bottom > bounds.bottom ) temp.y = bounds.bottom - temp.height;
			if( temp.top < bounds.top ) temp.y = bounds.y;
						
			target.x = temp.x;
			target.y = temp.y;
		}
		
	//--------------------------------------------------------------------------------
	//
	//	Event Handlers
	//
	//-------------------------------------------------------------------------------- 
		
		/**
		 * Called on MOUSE_MOVE durring a drag session.
		 * 
		 * @param event		The MouseEvent object from the MOUSE_MOVE event.
		 */
		protected function onDrag( event:MouseEvent ):void
		{	
			for( var i:String in this.list )
			{
				dragIt( this.list[ i ] );
			}
			
			event.updateAfterEvent();	
		}
		
	}
	
}